package us.versus.them.lines;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.text.TextField;

import flash.events.MouseEvent;

class App {
	var bitmap : Bitmap;
	var count  : Int;
	var busy   : Bool;
	var time   : TextField;
	var origW  : Float;

	public function new() {
		this.busy = true;
		this.setup( flash.Lib.current );
		this.count = 20000;
		this.busy = false;
	}

	////

	public function drawVerticalLine( bitmapData : BitmapData, x1 : Float, y1 : Float, x2 : Float, y2 : Float, c : UInt ) {
		// TODO: draw a verical line...
	}

	public function naive( bitmapData : BitmapData, x1 : Float, y1 : Float, x2 : Float, y2 : Float, c : UInt ) {
		if ( x1 > x2 ) {
			naive( bitmapData, x2, y2, x1, y1, c );
		} else {
			var run  = x2 - y1;
			if ( 0 == run ) {
				this.drawVerticalLine( bitmapData, x2, y2, x1, y1, c );
			} else {
				var rise = y2 - y1;
				var slope = rise / run;
				var x : Float = x1;
				var y : Float = y1;
				var tmp_y1 : Float;
				var tmp_y2 : Float = y1;
				if ( y1 < y2 ) {
					while ( x < x2 ) {
						tmp_y2 = y + slope;
						tmp_y1 = y;
						while ( tmp_y1 < tmp_y2 ) {
							bitmapData.setPixel( Math.floor( x ) , Math.floor( tmp_y1 ) , c );
							tmp_y1++;
						}
						y = tmp_y2;
						x++;
					}
				} else {
					while ( x < x2 ) {
						tmp_y2 = y + slope;
						tmp_y1 = y;
						while ( tmp_y1 > tmp_y2 ) {
							bitmapData.setPixel( Math.floor( x ) , Math.floor( tmp_y1 ) , 0xFF0000);
							tmp_y1--;
						}
						y = tmp_y2;
						x++;
					}
				}
			}
		}
	}

	public function bresenhamIntConvert( bitmapData : BitmapData, x1 : Float, y1 : Float, x2 : Float, y2 : Float, c : UInt ) {
		this.bresenhamInt(
			bitmapData
			, Math.floor( x1 )
			, Math.floor( y1 )
			, Math.ceil( x2 )
			, Math.ceil( y2 )
			, c
		);
	}

	// ported from http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Optimization
	public function bresenhamInt( 
		bitmapData : BitmapData
		, x0 : Int
		, y0 : Int
		, x1 : Int
		, y1 : Int
		, c : UInt 
	) {
		var steep : Bool = Math.abs( y1 - y0 ) > Math.abs( x1 - x0 );
		var tmp : Int;
		if ( steep ) {
			// swap x and y
			tmp = x0; x0 = y0; y0 = tmp; // swap x0 and y0
			tmp = x1; x1 = y1; y1 = tmp; // swap x1 and y1
		}
		if ( x0 > x1 ) {
			// make sure x0 < x1
			tmp = x0; x0 = x1; x1 = tmp; // swap x0 and x1
			tmp = y0; y0 = y1; y1 = tmp; // swap y0 and y1
		}
		var deltax : Int = x1 - x0;
		var deltay : Int = Math.floor( Math.abs( y1 - y0 ) );
		var error  : Int = Math.floor( deltax / 2 ); // this is a little hairy
		var y      : Int = y0;
		var ystep  : Int = if ( y0 < y1 ) 1 else -1;
		for ( x in x0 ... x1 ) {
			if ( steep ) {
				bitmapData.setPixel( y, x, c ) ;
			} else {
				bitmapData.setPixel( x, y, c );
			}
			error -= deltay;
			if ( error < 0 ) {
				y = y + ystep;
				error = error + deltax;
			}
		}
	}

	// ported from http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Optimization
	public function bresenham( 
		bitmapData : BitmapData
		, x0 : Float
		, y0 : Float
		, x1 : Float
		, y1 : Float
		, c : UInt 
	) {
		var steep : Bool = Math.abs( y1 - y0 ) > Math.abs( x1 - x0 );
		var tmp : Float;
		if ( steep ) {
			// swap x and y
			tmp = x0; x0 = y0; y0 = tmp; // swap x0 and y0
			tmp = x1; x1 = y1; y1 = tmp; // swap x1 and y1
		}
		if ( x0 > x1 ) {
			// make sure x0 < x1
			tmp = x0; x0 = x1; x1 = tmp; // swap x0 and x1
			tmp = y0; y0 = y1; y1 = tmp; // swap y0 and y1
		}
		var deltax : Float = x1 - x0;
		var deltay : Float = Math.abs( y1 - y0 );
		var error  : Float = deltax / 2;
		var y      : Float = y0;
		var ystep  : Float = if ( y0 < y1 ) 1 else -1;
		var x      : Float = x0;
		while ( x < x1 ) {
			if ( steep ) {
				bitmapData.setPixel( Math.floor( y ), Math.floor( x ), c ) ;
			} else {
				bitmapData.setPixel( Math.floor( x ), Math.floor( y ), c );
			}
			error -= deltay;
			if ( error < 0 ) {
				y = y + ystep;
				error = error + deltax;
			}
			x++;
		}
	}

	////

	public function test( drawLine : Dynamic ) {
		if ( !this.busy ) {
			this.time.text = 'drawing...';
			this.busy = true;
			var bitmapData = new BitmapData( 640, 480 );

			var x1 : Array< Float > = new Array< Float >();
			var y1 : Array< Float > = new Array< Float >();
			var x2 : Array< Float > = new Array< Float >();
			var y2 : Array< Float > = new Array< Float >();
			var c : Array< UInt > = new Array< UInt >();
			for ( i in 0 ... count ) {
				x1.push( Math.random() * 640 );
				x2.push( Math.random() * 640 );
				y1.push( Math.random() * 480 );
				y2.push( Math.random() * 480 );
				c.push( Math.floor( Math.random() * 0xFFFFFF ) );
			}

			var start : Date = Date.now();
			for ( i in 0 ... count ) {
				drawLine( bitmapData , x1[ i ] , y1[ i ] , x2[ i ] , y2[ i ] , c[ i ] );
			}
			var stop : Date = Date.now();

			this.bitmap.bitmapData = bitmapData;
			var diff : Float = ( stop.getTime() - start.getTime() ) / 1000.0;
			this.busy = false;
			this.time.text = count + ' in ' + diff + ' s';
			// puke:
			this.time.x = this.origW - 10 - this.time.textWidth;
		}
	}

	public function algorithm1( _ ) {
		this.test( naive );
	}

	public function algorithm2( _ ) {
		this.test( bresenhamIntConvert );
	}

	public function algorithm3( _ ) {
		this.test( bresenham );
	}

	////

	public function setup( movieClip : MovieClip ) {
		this.bitmap = new Bitmap();
		this.bitmap.x = 0;
		this.bitmap.y = 0;
		movieClip.addChild( this.bitmap );

		var i : Int = 0;
		var x : Int = 5;
		var y : Int = 5;
		var z : Int = 20;
		var c : Int = 0xFF0000;
		var messages = [ 'naive fp', 'bresenham int', 'bresenham fp' ];
		for ( method in [ algorithm1, algorithm2, algorithm3 ] ) { 
			movieClip.addChild( this.button( messages[ i++ ], x, y, z, z, c, method ) );
			y += 5 + z;
			c >>= 8;
		}

		var s = this.button( '↑click a method↑', x, y, z, z, 0xFFFFFF );
		this.time = cast( s.getChildAt( 0 ), TextField );
		this.origW = this.time.textWidth + 2 * z;
		movieClip.addChild( s );
	}

	public function button( txt : String, x : Int, y : Int, w : Int, h : Int, color : Int, ?onClick : Dynamic -> Void ) : Sprite {

		var textField : TextField = new TextField();
		textField.text = txt;
		textField.x = w;
		textField.y = 0;
		textField.backgroundColor = color;

		var sprite = new Sprite();
		sprite.x = x;
		sprite.y = y;
		sprite.graphics.lineStyle( 4, 0x000020 );
		sprite.graphics.beginFill( color );
		sprite.graphics.drawRect( 0, 0, textField.textWidth + w * 2, h );

		sprite.addChild( textField );

		if ( null != onClick ) {
			sprite.addEventListener( MouseEvent.CLICK , onClick );
		}
		return sprite;
	}
}
